use std::{ sync::Arc};

// simulate the nginx behavior
use anyhow::{Result};
use serde::{Serialize, Deserialize};
use tracing::{info, warn};
use tracing_subscriber::{filter::LevelFilter, fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, Layer as _};
use tokio::{
    net::{TcpStream, TcpListener}
    };


#[derive(Serialize, Deserialize, Debug)]
struct Config {
    listen_addr: String,
    upstream_addr: String,
}


#[tokio::main]
async fn main() -> Result<()>{

    let layer = Layer::new().pretty().with_filter(LevelFilter::INFO);
    tracing_subscriber::registry().with(layer).init();

    let config = resolve_config();
    let config = Arc::new(config);  // arc  只是clone 了reference , 因为不改它， 我感觉 可行， 比clone 好多了
    info!("Upstream: {}", config.upstream_addr);
    info!("Listen: {}", config.listen_addr);

    let listener = TcpListener::bind(&config.listen_addr).await?;

    loop{
        let (client, addr) = listener.accept().await?;
        let cloned_config = Arc::clone(&config);
        info!("Accept connectin from {}" , addr);
        tokio::spawn(async move {
            let upstream = TcpStream::connect(&cloned_config.upstream_addr).await?;
            proxy(client, upstream).await?;
            Ok::<(),anyhow::Error>(())
        });
    }


    // proxy client traffic to upstream
    #[allow(unreachable_code)]
    Ok(())
}



async fn proxy(mut client: TcpStream, mut upstream: TcpStream) ->Result<()> {
    let (mut client_reader, mut client_writer) = client.split();
    let (mut upstream_reader, mut upstream_writer) = upstream.split();

    let client_to_upstream = tokio::io::copy(&mut client_reader, &mut upstream_writer);  // io::copy(&mut client_reader, &mut upstream_writer);

    let upstream_to_client = tokio::io::copy(&mut upstream_reader, &mut client_writer);

    if let Err(e) = tokio::try_join!(client_to_upstream,upstream_to_client ) {
        warn!("error: {}",e);
    }

    Ok(())
}

// assume the config is resove via a file
fn resolve_config() ->Config{
    Config {
        listen_addr: "0.0.0.0:8081".to_string(),
        upstream_addr: "0.0.0.0:8080".to_string(),
    }
    
}